﻿/*
TreeGridModule is a liteGrid plug-in that adds tree-grid functionality.

Author: MBH
DateAuthored: 7/11/09  
*/

//A module that adds tree-grid support.
function TreeGridModule() {
	var base = this;

	//Called by liteGrid, this initializes the module and ties it
	//into the liteGrid plumbing.  liteGrid is the actual liteGrid
	//reference.
	base.initialize = function(liteGrid, options) {

		//Store a reference to the table, that way it can be accessed later, if needed.
		base.liteGrid = liteGrid;

		if (!("getChildData" in liteGrid.dataProvider)) {
			console.error("Specified data provider is not compatible with TreeGridModule, aborting initialization.");
			return;
		}

		base.dataProvider = liteGrid.dataProvider;

		//Find the utils column
		$(options.columns).each(function(index) {
			if (this.field == "utils") {
				base.utilsIndex = index;
			}
		});

		if (base.utilsIndex === undefined) {
			console.error("Unable to find a column named 'utils' to add the tree controls to. ");
			return;
		}

		//Initialize module-specific options
		base.options = $.extend({}, TreeGridModule.prototype.defaultOptions, options);

		//Register to receive rowBound events, that way we can insert the expander.
		liteGrid.$el.bind("rowBound", base.rowBound);
		//We also need to know if a row's ID changes
		liteGrid.$el.bind("rowIdChanging", base.rowIdChanging);

		//Add a helper method to liteGrid to help other modules find all the children of a row.
		liteGrid.getChildren = base.getChildren;
	}

	//Recursive function that finds all rows that are nested under the specified row.
	base.getChildren = function(parentId) {

		var children = base.liteGrid.$el.find("tr.child-of-" + parentId);

		var childRows = $([]);

		if (children.length > 0) {
			children.each(function() {
				var child = this;
				childRows = childRows.add(child).add(base.getChildren($(child).data("dataItem")[base.options.rowIdColumn]));
			});
		}

		return childRows;
	}

	//Gets the expander cell from the specified row.
	base.getUtilsCell = function(row) {
		return row.children(":eq(" + base.utilsIndex + ")");
	}

	//Callback that is invoked whenever a row has been bound in the grid.
	base.rowBound = function(event, row, dataItem, index) {

		//The expander goes here.
		var expanderCell = base.getUtilsCell(row);

		//Add a toolbox that will hold the expander control.
		var controlDiv = $("<div class='controls'></div>");
		//This is the actual expander icon.
		var expanderIcon = $("<span class='ui-icon expander' />");

		//Only add an expander if the row has children.
		if (dataItem.HasChildren === true) {
			//If the row is being rebound, it might have been expanded already.
			if (row.attr("expanded") === "true") {
				expanderIcon.addClass("ui-icon-minusthick")
					   .toggle(function() { base.rowCollapsed(dataItem[base.options.rowIdColumn]); },
							   function() { base.rowExpanded(dataItem[base.options.rowIdColumn]); }
							 );
			}
			//Otherwise it wasn't expanded, and the first click should open it.
			else {
				expanderIcon.addClass("ui-icon-plusthick")
					   .toggle(function() { base.rowExpanded(dataItem[base.options.rowIdColumn]); },
							   function() { base.rowCollapsed(dataItem[base.options.rowIdColumn]); }
							 );
			}
		}
		//Otherwise mark it as not being expandable
		else {
			expanderIcon.addClass("ui-icon-bullet");
		}

		//If the row is being rebound because it was saved, the expander needs to be repadded.
		if (row.attr("expanderPadding") != undefined) {
			expanderIcon.css("margin-left", row.attr("expanderPadding"));
		}

		//Add things to the DOM.
		expanderCell.append(controlDiv.append(expanderIcon));
	}

	//Callback that is invoked whenever a row is collapsed.
	base.rowCollapsed = function(parentId) {

		//Grab a reference to the parent, the first row we add goes after it.
		var parent = base.liteGrid.$el.find("tr#row-id-" + parentId);

		//Mark the row as collapsed
		parent.attr("expanded", false);

		//Change the expander image
		var parentExpander = base.getUtilsCell(parent).find("span.expander")
				.removeClass("ui-icon-minusthick").addClass("ui-icon-plusthick");

		var children = base.getChildren(parentId);
		//Mark the immediate children as collapsed.
		children.filter(".child-of-" + parentId).not(".disabled").addClass("hidden");
		//Now hide everything from view.
		children.hide();

		//Signal that the table has changed.
		base.liteGrid.$el.trigger("tableUpdated", base.liteGrid);
	}

	//Callback that is invoked whenever the expando-link is clicked.
	base.rowExpanded = function(parentId) {

		//Grab a reference to the parent, the first row we add goes after it.
		var parent = base.liteGrid.$el.find("#row-id-" + parentId);

		//Mark the row as expanded
		parent.attr("expanded", true);

		//Change the expander image
		var parentExpander = base.getUtilsCell(parent).find("span.expander")
				.removeClass("ui-icon-plusthick").addClass("ui-icon-minusthick");

		//Determine if the children are already loaded. If they are, we don't need to re-retrieve them.
		if (parent.hasClass("children-loaded")) {

			var children = base.getChildren(parentId);
			//Mark the immediate children as shown.
			children.filter(".child-of-" + parentId).not(".disabled").removeClass("hidden");
			//Now actually show any rows that should be visible.  Expanding a row that has expanded children
			//means that we may be showing children at multiple levels.
			children.not(".hidden, .disabled").show();

			//Signal that the table has changed.
			base.liteGrid.$el.trigger("tableUpdated", base.liteGrid);

			return;
		}

		//Otherwise, we have to load and show the children.

		//Get the padding of the parent, we need it in order to add padding to the children.
		var expanderPadding = parseInt(parentExpander.css("margin-left"));
		if (isNaN(expanderPadding)) {
			expanderPadding = 0;
		}

		//Grab the child data from the provider.
		base.dataProvider.getChildData(parentId,
		//Success handler
			function(dataSet) {
				//This is the row we're adding the next row after.  
				var targetRow = parent;

				//Add the items to the table
				$(dataSet.dataItems).each(function() {

					//We want to insert the items in order, so the target for the next
					//iteration is the newly inserted row.
					var newRow = base.liteGrid.insertRowAfter(this, targetRow);

					//Mark the row as a child of the parent
					newRow.addClass("child-of-" + parentId).attr("parentId", parentId);

					//Adjust the padding of the child's expander.  It should be more than
					//it's parent.
					var padding = (expanderPadding + base.options.paddingPerLevel) + "px";
					base.getUtilsCell(newRow).find("span.expander").css("margin-left", padding);

					//Store the padding in an expando attribute, too.  We'll need it to restore the expander
					//after a save.
					newRow.attr("expanderPadding", padding);

					//Update target, we want the next row added to go under
					//the row that was just added.
					targetRow = newRow;
				});
			},
		//Error handler
			function(request, error, status) {
				var event = $.Event("TreeGridLoadError");
				event.request = request;
				event.error = error;
				event.status = status;

				base.liteGrid.raiseErrorEvent(event);
			}
		);

		//Mark the parent's children as having been loaded.
		parent.addClass("children-loaded");
	}

	//Fired whenever a row's ID changes.
	base.rowIdChanging = function(event) {

		//Update parent/child relationships, if any.
		$("tr.child-of-" + event.oldId)
			.attr("parentid", event.newId)
			.removeClass("child-of-" + event.oldId)
			.addClass("child-of-" + event.newId);
	}
}
TreeGridModule.prototype.defaultOptions = {
	paddingPerLevel: 5
};